package core;

public class ProcessorClass implements java.lang.Runnable, core.interfaces.InterruptInterface, core.interfaces.MemoryCallbackInterface, core.interfaces.RegisterCallbackInterface{
	public core.PsrClass psr;
	public core.AluClass alu;
	public core.RegisterClass rb;
	public core.MemoryClass mem;
	public core.ControlUnitClass cu;

	public core.interfaces.CoreCallbackInterface callBack = null;
	public core.interfaces.InterruptInterface iInterface = null;
	public devices.Timer timer = null;
	public devices.NetDevice netDevice = null;

	private util.SegmentManagerClass programSegments = new util.SegmentManagerClass();

	public static int defaultSP = 0xFFCF;
	private int freq = 10;

	private int baseSP = core.ProcessorClass.defaultSP;

	private boolean procStopped = true;
	private java.lang.Thread thread = null;
	private java.lang.Object lock = new java.lang.Object();

	public ProcessorClass(){
		this.psr = new core.PsrClass();
		this.alu = new core.AluClass(this.psr);
		this.rb = new core.RegisterClass();
		this.rb.setSP((short)this.baseSP);
		this.rb.setCallBack(this);
		this.mem = new core.MemoryClass();
		this.mem.setCallBack(this);
		this.cu = new core.ControlUnitClass(this.alu, this.rb, this.mem, this.psr);
		this.cu.setInterruptInterface(this);
		this.thread = new java.lang.Thread(this);
		this.thread.start();
	}
	public void setCallBack(core.interfaces.CoreCallbackInterface cObject){
		this.callBack = cObject;
	}
	public void setInterruptInterface(core.interfaces.InterruptInterface iObject){
		this.iInterface = iObject;
	}
	public void setTimer(devices.Timer timer){
		this.timer = timer;
	}
	public void setNetDevice(devices.NetDevice netDevice){
		this.netDevice = netDevice;
	}
	public void setBaseSP(int newBaseSP){
		this.baseSP = newBaseSP;
		this.rb.setSP((short)this.baseSP);
		if(this.callBack != null){
			this.callBack.registerChanged();
		}
	}
	public int getBaseSP(){
		return(this.baseSP);
	}

	public void setInstruction(int address, core.InstructionClass instruction){
		if(instruction != null){
			//If the instruction we are going to substitute has format 4, convert second part to a NOP
			if (new core.InstructionClass(this.mem.getWord(address)).inst.getFormat() == 4){
				this.mem.setWord(address + 1, new core.InstructionClass("NOP").getByteCode());
			}
			this.mem.setWord(address, instruction.getByteCode());
			this.programSegments.addSegment(address, address);
			if(instruction.inst.getFormat() == 4){
				this.mem.setWord(address + 1, instruction.getByteCode2());
				this.programSegments.addSegment(address, address+1);
			}
			if(this.callBack != null){
				this.callBack.allChanged();
			}
		}
	}
	public void loadFile(java.io.File file){
		try {
			java.lang.String line = null;
			java.lang.String lineOrig = null;
			java.io.InputStream is = new java.io.FileInputStream(file);
			java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is));
			try{
				int lineCounter = 0;
				short location = 0x0000;
				short ilocation = 0x0000;
				core.InstructionClass auxInstruction = null;
				while((lineOrig = br.readLine()) != null){
					lineCounter++;
					lineOrig = lineOrig.trim();
					line = lineOrig.split("#")[0];
					if(line.length()<=0){
					}else if(line.startsWith("ORG ")){
						line = line.trim();
						java.lang.String[] toks = line.split(" ");
						if(toks.length == 2){
							try{
								this.programSegments.addSegment(ilocation, location-1);
								location = ilocation = java.lang.Integer.decode(toks[1]).shortValue();
								//System.out.printf("New location: 0x%04X\n", location);
							}catch(java.lang.NumberFormatException e){}
						}
					}else{
						auxInstruction = new core.InstructionClass(line);
						//System.out.println("\t" + i + ": " + auxInstruction.getAssembly() + "\t" + line);
						mem.setWord(location, auxInstruction.getByteCode());
						location++;
						if(auxInstruction.inst.getFormat() == 4){
							mem.setWord(location, auxInstruction.getByteCode2());
							location++;
						}
						if(auxInstruction.error){
							if(this.callBack != null){
								this.callBack.error(String.format("Error in line %d: %s", lineCounter, line));
							}
							System.err.printf("Error in line %d: %s\n", lineCounter, line);
							break;
						}
					}
				}
				this.programSegments.addSegment(ilocation, location-1);
				//this.programSegments.print();
			}catch(java.io.IOException e){
				
			}
		} catch (java.io.FileNotFoundException e) {
			e.printStackTrace();
		}
		if(this.callBack != null){
			this.callBack.allChanged();
		}
	}

	public void saveFile(java.io.File file){
		try {
			java.io.PrintStream ps = new java.io.PrintStream(file);
			util.SegmentClass[] segs = this.programSegments.getSegments();
			for(int i=0; i<segs.length; i++){
				//print ORG (segs[i].a)
				ps.printf("ORG 0x%04X\n", segs[i].a);
				java.util.Vector<java.lang.Integer> v = this.mem.getMemoryVector((short)segs[i].a, (short)(segs[i].b - segs[i].a + 1));
				java.util.Iterator<java.lang.Integer> it = v.iterator();
				java.lang.Integer aux;
				core.InstructionClass auxInstruction = null;
				while(it.hasNext()){
					aux = it.next();
					short memValue = (short)(aux.intValue() & 0x0000FFFF);
					auxInstruction = new core.InstructionClass(memValue);
					if(auxInstruction.inst.getFormat() == 4 && it.hasNext()){
						aux = it.next();
						memValue = (short)(aux.intValue() & 0x0000FFFF);
						auxInstruction.setByteCode2(memValue);
					}
					ps.println(auxInstruction.getAssembly());
				}
			}
		} catch (java.io.FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	synchronized public void step(){
		//this.cu.processNextInstructionVerbose();
		this.psr.clearMods();
		this.cu.processNextInstruction();
		if(this.callBack != null){
			this.callBack.allChanged();
		}
	}

	public void run() {
		while(true){
			synchronized(lock){
				while(this.procStopped){
					try {
						this.lock.wait();
					} catch (InterruptedException e) {}
				}
			}
			while(!this.cu.STOP && !this.procStopped){
				this.step();
				try {
					java.lang.Thread.sleep(1000/this.freq);
				} catch (InterruptedException e) {}
			}
			this.procStopped = true;
			this.cu.STOP = false;
			if(this.callBack != null){
				this.callBack.procStopped();
			}
		}
	}
	public void start(){
		this.procStopped = false;
		synchronized(lock){
			this.lock.notifyAll();
		}
	}
	public void stop(){
		this.procStopped = true;
	}
	public void hardReset(){
		int i;
		this.timer.reset();
		for(i=0; i<this.rb.getMax(); i++){
			this.rb.setRegister(i, (short)0);
		}
		this.rb.setSP((short)this.baseSP);
		for(i=0; i<this.mem.getMax(); i++){
			this.mem.setWord(i, (short)0);
		}
		this.programSegments.clearSegments();
		//TODO: decide interrupt flags
		this.psr.setFlags(0);
		this.cu.setPC((short)0);
		this.cu.STOP = false;
		if(this.callBack != null){
			this.callBack.allChanged();
		}
	}
	public void progReset(){
		int i;
		this.timer.reset();
		for(i=0; i<this.rb.getMax(); i++){
			this.rb.setRegister(i, (short)0);
		}
		this.rb.setSP((short)this.baseSP);
		for(i=0; i<this.mem.getMax(); i++){
			this.mem.setWord(i, (short)0);
		}
		this.programSegments.clearSegments();
		//TODO: decide about interrupt flags
		this.psr.setFlags(0);
		this.cu.setPC((short)0);
		this.cu.STOP = false;
		if(this.callBack != null){
			this.callBack.memoryChanged();
			this.callBack.PCChanged();
			this.callBack.registerChanged();
		}
	}
	public void reset(){
		int i;
		//Registers
		this.timer.reset();
		for(i=0; i<this.rb.getMax(); i++){
			this.rb.setRegister(i, (short)0);
		}
		this.rb.setSP((short)this.baseSP);
		//Memory (don't touch program parts)
		util.SegmentClass[] auxSegs = this.programSegments.getSegments();
		int auxMem1 = 0x0;
		int j=0;
		for(i=0; (i<auxSegs.length)&&(i<this.mem.getMax()); i++){
			for(j=auxMem1; j<auxSegs[i].a-1; j++){
				this.mem.setWord(j, (short)0);
			}
			auxMem1 = auxSegs[i].b+1;
		}
		for(j=auxMem1; j<this.mem.getMax(); j++){
			this.mem.setWord(j, (short)0);
		}
		//PSR, PC and STOP flags
		//TODO: decide about interrupt flags
		this.psr.setFlags(0);
		this.cu.setPC((short)0);
		this.cu.STOP = false;
		if(this.callBack != null){
			this.callBack.allChanged();
		}
	}
	public void raiseInterruptLine(int line){
		switch(line){
			case 0:
				this.cu.setInterruptBit(core.ControlUnitClass.Interrupts.RESET.getBit());
				if(this.iInterface != null){
					iInterface.raiseInterruptLine(line);
				}
				break;
			case 1:
				if(this.psr.getTIM() == 0){
					this.cu.setInterruptBit(core.ControlUnitClass.Interrupts.TIMER.getBit());
					if(this.iInterface != null){
						iInterface.raiseInterruptLine(line);
					}
				}else{
					System.err.println("Timer interrupt called but not raised because it's masked");
				}
				break;
			case 2:
				if(this.psr.getHIM() == 0){
					this.cu.setInterruptBit(core.ControlUnitClass.Interrupts.HIGH.getBit());
					if(this.iInterface != null){
						iInterface.raiseInterruptLine(line);
					}
				}else{
					System.err.println("High interrupt called but not raised because it's masked");
				}
				break;
			case 3:
				if(this.psr.getLIM() == 0){
					this.cu.setInterruptBit(core.ControlUnitClass.Interrupts.LOW.getBit());
					if(this.iInterface != null){
						iInterface.raiseInterruptLine(line);
					}
				}
				break;
			case 4:
				this.cu.setInterruptBit(core.ControlUnitClass.Interrupts.SOFTWARE.getBit());
				if(this.iInterface != null){
					iInterface.raiseInterruptLine(line);
				}
				break;
		}
	}
	public void lowerInterruptLine(int line){
		if(this.iInterface != null){
			iInterface.lowerInterruptLine(line);
		}

	}
	public void memoryAccessed(int address) {
		if(this.callBack != null){
			this.callBack.memoryAccessed(address);
		}
	}
	public void memoryChanged() {
		if(this.callBack != null){
			this.callBack.memoryChanged();
		}
	}
	public void memoryChanged(int address) {
		if(this.callBack != null){
			this.callBack.memoryChanged(address);
		}
		if(this.timer != null){
			if(this.timer.getBaseAddress() == address){
				this.timer.reloadConfiguration();
			}
		}
		if(this.netDevice != null){
			if(this.netDevice.getBaseAddress() == (address & 0x0000FFF0)){
				this.netDevice.memoryChanged(address);
			}
		}
	}
	public void registerAccessed(int r) {
		if(this.callBack != null){
			this.callBack.registerAccessed(r);
		}
	}
	public void registerChanged() {
		if(this.callBack != null){
			this.callBack.registerChanged();
		}
	}
	public void registerChanged(int r) {
		if(this.callBack != null){
			this.callBack.registerChanged(r);
		}
	}
	public int getFreq(){
		return(this.freq);
	}
	public void setFreq(int freq){
		this.freq = freq;
	}
}
